import { Component, Host, h, VNode, Prop, ComponentInterface, Element, Method, forceUpdate, State, Listen } from '@stencil/core';
import { Moment } from 'moment';
import interact from 'interactjs';
import { Subscription } from 'rxjs';
import { calendarUtil } from '@/utils/calendar-util/calendar-util';
import { IBzCalendarWeekController } from '../core/controller/ibz-calendar-week-controller';
import { IBzCalendarWeekNode } from '../core/interface/ibz-calendar-week-node';

/**
 * 「周」模式绘制项
 *
 * @export
 * @class IBzWeekDayItem
 */
@Component({
    tag: 'ibz-week-day-item',
    styleUrl: 'ibz-week-day-item.scss'
})
export class IBzWeekDayItem implements ComponentInterface {

    /**
     * 组件DOM实例
     *
     * @type {HTMLElement}
     * @memberof IBzWeekDayItem
     */
    @Element()
    public el: HTMLElement;

    /**
     * 拖拽节点DOM实例
     *
     * @protected
     * @type {HTMLDivElement}
     * @memberof IBzWeekDayItem
     */
    protected handelEl: HTMLDivElement;

    /**
     * 已订阅的Rxjs
     *
     * @protected
     * @type {Subscription[]}
     * @memberof IBzWeekDayItem
     */
    protected subs: Subscription[] = [];

    /**
     * 拖拽配置
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected draggableConfig: any = {};

    /**
     * 当前高度
     *
     * @protected
     * @type {number}
     * @memberof IBzWeekDayItem
     */
    @State()
    protected height: number;

    /**
     * 当前距离顶部
     *
     * @protected
     * @type {number}
     * @memberof IBzWeekDayItem
     */
    @State()
    protected top: number;

    /**
     * 是否悬浮在当前组件之上
     *
     * @protected
     * @type {boolean}
     * @memberof IBzWeekDayItem
     */
    protected isHover: boolean = false;

    /**
     * 订阅鼠标进入
     *
     * @memberof IBzWeekDayItem
     */
    @Listen('mouseenter')
    public onmouseenter(): void {
        this.isHover = true;
    }

    /**
     * 订阅鼠标离开
     *
     * @memberof IBzWeekDayItem
     */
    @Listen('mouseleave')
    public onmouseleave(): void {
        this.isHover = false;
    }

    /**
     * 父是否悬浮
     *
     * @type {boolean}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public isParentHover: boolean = false;

    /**
     * 日历「周」模式控制器
     *
     * @type {IBzCalendarWeekController}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public $c: IBzCalendarWeekController;

    /**
     * 日历项数据
     *
     * @type {IBzCalendarWeekNode}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public item: IBzCalendarWeekNode;

    /**
     * 当前日期
     *
     * @type {string}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public date: string;

    /**
     * 今日起始时间
     *
     * @type {Moment}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public todayStart: Moment;

    /**
     * 今日结束时间
     *
     * @type {Moment}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public todayEnd: Moment;

    /**
     * 天容器应有宽度
     *
     * @type {number}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public width: number;

    /**
     * 天数在当前日历的下标
     *
     * @type {number}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public index: number;

    /**
     * 当前数据在今日的展示层级
     *
     * @type {number}
     * @memberof IBzWeekDayItem
     */
    @Prop()
    public level: number;

    /**
     * 组件加载之前
     *
     * @memberof IBzWeekDayItem
     */
    public componentWillLoad(): void {
        this.initControllerEvent();
        this.recalculate();
    }

    /**
     * 组价加载完毕
     *
     * @memberof IBzWeekDayItem
     */
    public componentDidLoad(): void {
        this.initDragConfig();
        this.initDrag();
    }

    /**
     * 组件取消DOM连接
     *
     * @memberof IBzWeekDayItem
     */
    public disconnectedCallback(): void {
        if (!this.el.isConnected) {
            this.subs.forEach((item) => {
                item.unsubscribe();
            });
        }
    }

    /**
     * 拖拽配置参数初始化
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected initDragConfig(): void {
        this.draggableConfig = {
            modifiers: [
                // 指定单次拖拽跳动距离
                interact.modifiers.snap({
                    targets: [
                        interact.createSnapGrid({ x: this.width, y: this.$c.layoutConfig.dragOffset })
                    ],
                    range: Infinity,
                    relativePoints: [{ x: 0, y: 0 }]
                })
            ],
            inertia: false,
            autoScroll: true,
            onstart: () => {
                this.el.classList.add('dragging');
                this.item.dragging = true;
            },
            onend: () => {
                this.el.classList.remove('dragging');
                this.item.dragging = false;
                this.$c.nodeNext(this.item);
                this.$c.externalItemChange(this.item);
                setTimeout(() => {
                    this.$c.dayChange(this.todayStart);
                });
            }
        };
    }

    /**
     * 初始化监听控制器事件
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected initControllerEvent(): void {
        this.subs.push(this.$c.changeNodeObservable().subscribe((item: IBzCalendarWeekNode) => {
            if (item.id === this.item.id) {
                if (!calendarUtil.isOverlapsTime(this.$c.calcDayStartDate(this.date), this.$c.calcDayEndDate(this.date), item.startMoment, item.endMoment)) {
                    this.$c.dayChange(this.todayStart);
                } else {
                    this.recalculate();
                }
            }
        }));
    }

    /**
     * 拖拽初始化
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected initDrag(): void {
        interact(this.el).draggable(this.draggableConfig).on('dragmove', (event) => {
            const x: number = event.dx;
            if (x !== 0 && x % this.width === 0) {
                const days = x / this.width;
                if (days !== 0) {
                    this.item.startMoment.add(days, 'days');
                    this.item.endMoment.add(days, 'days');
                    this.$c.dayChange(this.todayStart);
                    this.$c.dayChange(this.todayStart.clone().add(days, 'days'));
                    this.$c.dayChange(this.todayStart.clone().add(-days, 'days'));
                    this.$c.nodeNext(this.item);
                    return;
                }
            }
            const y: number = event.dy;
            if (y !== 0 && y % this.$c.layoutConfig.dragOffset === 0) {
                const minutes = Math.abs(y / this.$c.layoutConfig.dragOffset * this.$c.option.dragOffsetMinutes);
                if (y > 0) {
                    this.changeStartAndEndDate(minutes);
                } else {
                    this.changeStartAndEndDate(-minutes);
                }
            }
        });
        interact(this.handelEl).draggable({ ...this.draggableConfig, startAxis: 'y', lockAxis: 'y' }).on('dragmove', (event) => {
            const y: number = event.dy;
            if (y !== 0 && y % this.$c.layoutConfig.dragOffset === 0) {
                const minutes = Math.abs(y / this.$c.layoutConfig.dragOffset * this.$c.option.dragOffsetMinutes);
                if (y > 0) {
                    this.changeEndDate(minutes);
                } else {
                    this.changeEndDate(-minutes);
                }
            }
        });
    }

    /**
     * 变更起始结束时间
     *
     * @protected
     * @param {number} minutes 需要变更的时间，单位「分钟」
     * @returns {void}
     * @memberof IBzWeekDayItem
     */
    protected changeStartAndEndDate(minutes: number): void {
        const newStartMoment = this.item.startMoment.clone().add(minutes, 'minutes');
        const newEndMoment = this.item.endMoment.clone().add(minutes, 'minutes');
        if (minutes > 0) {
            if (newStartMoment.isAfter(this.todayEnd)) {
                return;
            }
        } else {
            if (newEndMoment.isSameOrBefore(this.todayStart)) {
                return;
            }
        }
        if (!this.$c.option.allowTimeOverlap && this.$c.isOverlapsTodayTime(this.item, this.todayStart, this.todayEnd, newStartMoment, newEndMoment)) {
            return;
        }
        this.$c.changeNodeStartAndEndDate(newStartMoment, newEndMoment, this.item);
        this.changeItem();
    }

    /**
     * 变更结束时间
     *
     * @protected
     * @param {number} minutes
     * @returns {void}
     * @memberof IBzWeekDayItem
     */
    protected changeEndDate(minutes: number): void {
        const newEndMoment = this.item.endMoment.clone().add(minutes, 'minutes');
        if (newEndMoment.isSameOrBefore(this.item.startMoment) || newEndMoment.isSameOrBefore(this.todayStart)) {
            return;
        }
        if (!this.$c.option.allowTimeOverlap && this.$c.isOverlapsTodayTime(this.item, this.todayStart, this.todayEnd, this.item.startMoment, newEndMoment)) {
            return;
        }
        this.$c.changeNodeEndDate(newEndMoment, this.item);
        this.changeItem();
    }

    /**
     * 变更当前数据
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected changeItem(): void {
        if (this.item.startMoment.isBefore(this.todayStart) || this.item.endMoment.isAfter(this.todayEnd)) {
            this.$c.nodeNext(this.item);
        } else {
            this.recalculate();
        }
    }

    /**
     * 重新计算内容
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected recalculate(): void {
        this.calcItemHeightAndTop(this.item);
        this.tick();
    }

    /**
     * 计算项的高度与上偏移值
     *
     * @protected
     * @param {IBzCalendarWeekNode} item 数据项
     * @memberof IBzCalendarWeekController
     */
    protected calcItemHeightAndTop(item: IBzCalendarWeekNode): void {
        // 转为moment对象
        // 计算与起始时间的差值
        let dVal = 0;
        let dVal2 = this.todayEnd.diff(this.todayStart, 'minutes');
        if (this.todayStart.isAfter(item.startMoment)) {
            this.top = 0;
            this.$c.dayChange(this.todayStart.clone().add(-1, 'days'));
        } else {
            dVal = item.startMoment.diff(this.todayStart, 'minutes');
            // 计算分割层数
            const num = dVal / this.$c.option.timeSplitScale;
            // 计算上偏移值
            this.top = this.$c.layoutConfig.lineHeight * num;
        }
        let isDayChange = false;
        if (this.todayEnd.isAfter(item.endMoment)) {
            dVal2 = item.endMoment.diff(this.todayStart, 'minutes');
        } else {
            isDayChange = true;
        }
        // 计算分割层数
        const num2 = (dVal2 - dVal) / this.$c.option.timeSplitScale;
        // 计算高度
        const height = this.$c.layoutConfig.lineHeight * num2;
        this.height = height;
        if (isDayChange) {
            this.$c.dayChange(this.todayStart.clone().add(1, 'days'));
        }
    }

    /**
     * 项点击
     *
     * @protected
     * @memberof IBzWeekDayItem
     */
    protected onClick(e: MouseEvent): void {
        e.stopPropagation()
        this.$c.externalItemClick(this.item);
    }

    /**
     * 绘制内容
     *
     * @protected
     * @returns {VNode}
     * @memberof IBzWeekDayItem
     */
    protected renderContent(): VNode {
        return <div class='ibz-week-day-item-content'>
            <div class='content-text'>{this.item.text}</div>
            <div class='content-time'>
                {this.todayStart.isBefore(this.item.startMoment) ? this.item.startMoment.format('HH:mm:ss') : this.todayStart.format('HH:mm:ss')}
                &nbsp;-&nbsp;
                {this.todayEnd.isAfter(this.item.endMoment) ? this.item.endMoment.format('HH:mm:ss') : this.todayEnd.format('HH:mm:ss')}
            </div>
        </div>;
    }

    /**
     * 计算样式
     *
     * @protected
     * @returns {*}
     * @memberof IBzWeekDayItem
     */
    protected calcStyle(): any {
        const style: any = {
            top: this.top + 'px',
            height: this.height + 'px',
            'z-index': this.level
        };
        if (this.item.dragging) {
            style.left = (this.width * this.index) + 'px';
            style.width = this.width + 'px';
        } else {
            style.left = (this.width * this.index) + (this.level * 20) + 'px';
            style.width = `${this.width - 10 - (this.level * 20)}px`;
        }
        return style;
    }

    /**
     * 绘制项
     *
     * @returns {VNode}
     * @memberof IBzWeekDayItem
     */
    public render(): VNode {
        return (
            <Host onClick={(e) => this.onClick(e)} class={{ 'ibz-week-day-item': true, 'dragging': this.item.dragging }} style={this.calcStyle()}>
                {this.renderContent()}
                <div ref={(ref) => this.handelEl = ref} class={{ 'drag-handle': true, 'hidden': this.todayEnd.isBefore(this.item.endMoment) }}></div>
            </Host>
        );
    }

    /**
     * 强制脏检查更新
     *
     * @returns {Promise<void>}
     * @memberof IBzWeekDayItem
     */
    @Method()
    public async tick(): Promise<void> {
        forceUpdate(this);
    }

}
